/* Copyright (c) 2008, The Visigoth Software Society
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notices,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name Visigoth Software Society, FreeCC, Sun Microsystems, Inc. 
 *       nor the names of any other contributors may be used to endorse 
 *       or promote products derived from this software without specific prior written 
 *       permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.visigoths.freecc.parser;

import org.visigoths.freecc.codegen.java.*;
import static org.visigoths.freecc.parser.FreeCCParserConstants.*;
import java.util.*;
import freemarker.template.*;
import java.lang.reflect.*;

public class BaseNode extends SimpleNode implements TemplateHashModel, Iterable<Node> {
    
    static Node[] EMPTY_NODE_LIST = new Node[0];
    
    public BaseNode(int id) {
        super(id);
        children = EMPTY_NODE_LIST;
    }
    
    static public BaseNode jjtCreate(int id) {
        switch (id) {
            case JJTCLASSORINTERFACEBODYDECLARATION :
                return new ClassOrInterfaceBodyDeclaration();
            case JJTCONSTRUCTORDECLARATION :
            case JJTMETHODDECLARATION :
                return new MethodDeclaration();
            case JJTTYPEPARAMETER : 
                return new TypeParameter();
            case JJTTYPEPARAMETERS :
                return new TypeParameterList();
            case JJTCLASSORINTERFACETYPE :
                return new ObjectType();
            case JJTIMPLEMENTSLIST :
                return new ImplementsList();
            case JJTEXTENDSLIST :
                return new ExtendsList();
            case JJTTYPEDECLARATION : 
                return new TypeDeclaration();
            case JJTFORMALPARAMETER :
                return new FormalParameter();
            case JJTFORMALPARAMETERS :
                return new FormalParams();
            case JJTARGUMENTS :
                return new InvocationArgs();
            case JJTTHROWSLIST :
                return new ThrowsList();
            case JJTFINALLYBLOCK : 
                return new FinallyBlock();
            case JJTCATCHBLOCK :
                return new CatchBlock();
            case JJTTRYSTATEMENT : 
                return new TryStatement();
            case JJTCLASSORINTERFACEBODY :
                return new ClassOrInterfaceBody();
            case JJTIMPORTDECLARATION :
                return new ImportDeclaration();
            case JJTPACKAGEDECLARATION :
                return new PackageDeclaration();
            case JJTCOMPILATIONUNIT :
                return new CompilationUnit();
            case JJTPRIMARYEXPRESSION :
            case JJTEXPRESSION :
                return new Expression(id);
            case JJTBLOCK :
                return new CodeBlock();
            case JJTRETURNTYPE :
                return new ReturnType();
            case JJTNAME :
                return new Name();
            default :
                if (id >= JJTCOMPILATIONUNIT) {
                    return new JavaCode(id);                
                }
                return new BaseNode(id);
        }
    }
    
    public int getId() {
        return id;
    }
    
    protected List<Token> getTokens() {
        List<Token> result = new ArrayList<Token>();
        if (children != null) {
            for (Node child : children) {
                if (child instanceof Token) {
                    result.add((Token) child);
                } else {
                    result.addAll(((BaseNode)child).getTokens());
                }
            }
        }
        return result;
    }
    
    protected List<Token> getRealTokens() {
        List<Token> result = new ArrayList<Token>();
        for (Token token : getTokens()) {
            if (token.next != null && token.next.specialToken != token) {
                result.add(token);
            }
        }
        return result;
    }
    
    protected String getLiteralText() {
        StringBuilder buf = new StringBuilder();
        for (Token t : getTokens()) {
            buf.append(t);
            if (t.image.equals("extends")||t.image.equals("implements")) {
                // STOPGAP kludge REVISIT
                buf.append(" "); 
            }
            appendTrailingWhitespace(buf, t);
        }
        return buf.toString();
    }
    
    protected String getInnerText() {
        StringBuilder buf = new StringBuilder();
        List<Token> tokens = getTokens();
        boolean outputOpeningBrace = false;
        for (int i=0; i<tokens.size() -1; i++) {
            Token t = tokens.get(i);
            if (outputOpeningBrace) {
                buf.append(t.getAsString());
            } else if (t.kind == LBRACE) {
                outputOpeningBrace = true;
            }
            appendTrailingWhitespace(buf, t);
        }
        return buf.toString();
    }
    
    private static void appendTrailingWhitespace(StringBuilder buf, Token t) {
        Token next = t.next;
        if (next != null) {
            while (next.specialToken != null) {
                next = next.specialToken;
            }
            if (next.beginLine == t.endLine) {
                int spaces = next.beginColumn - t.endColumn -1;
                for (int i=0; i<spaces; i++) {
                    buf.append((char) ' ');
                }
            } else {
                int newlines = next.beginLine - t.endLine;
                for (int i=0; i<newlines;i++) {
                    buf.append((char) '\n');
                }
                for (int i=1; i<next.beginColumn; i++) {
                    buf.append((char)' ');
                }
            }
        }
    }
    
    public TemplateModel get(String key) throws TemplateModelException {
        Method method = null;
        String methodName = "get" + key.substring(0, 1).toUpperCase() + key.substring(1);
        try {
            method = this.getClass().getMethod(methodName);
        } catch (NoSuchMethodException e) {
            return null;
        }
        try {
            Object result = method.invoke(this);
            return Configuration.getCurrentObjectWrapper().wrap(result);
        } catch (IllegalAccessException e) {
            throw new TemplateModelException(e);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause instanceof TemplateModelException) {
                throw (TemplateModelException) cause;
            }
            if (cause instanceof Exception) {
                throw new TemplateModelException((Exception) cause);
            }
            throw (Error) cause;
        }
    }
    
    public boolean isEmpty() {
        return false;
    }
    
    
    
    
    // NB: This is not thread-safe
    // If children could change out from under you,
    // you have a problem.
    public ListIterator<Node> iterator() {
        return new ListIterator<Node>() {
            int current = -1;
            boolean justModified;
            
            public boolean hasNext() {
                return current+1 < children.length;
            }
            
            public Node next() {
                justModified = false;
                return children[++current];
            }
            
            public Node previous() {
                justModified = false;
                return children[--current];
            }
            
            public void remove() {
                if (justModified) throw new IllegalStateException();
                Node[] newList = new Node[children.length -1];
                for (int i=0; i<current;i++) {
                    newList[i] = children[i];
                }
                for (int i=current+1; i<children.length; i++) {
                    newList[i-1] = children[i];
                }
                --current;
                BaseNode.this.children = newList;
                justModified = true;
            }
            
            public void add(Node n) {
                if (justModified) throw new IllegalStateException();
                Node[] newList = new Node[children.length +1];
                for (int i=0; i<=current; i++) {
                    newList[i] = children[i];
                }
                newList[current+1] = n;
                for (int i=current+1; i<children.length; i++) {
                    newList[i+1] = children[i];
                }
                BaseNode.this.children = newList;
                justModified = true;
            }
            
            public boolean hasPrevious() {
                return current >0;
            }
            
            public int nextIndex() {
                return current + 1;
            }
            
            public int previousIndex() {
                return current - 1;
            }
            
            public void set(Node n) {
                children[current] = n;
            }
        };
    }
    
    public void prepend(Node n) {
        ListIterator<Node> iterator = iterator();
        iterator.add(n);
    }
    
}
